/////////////////////////////////////////////////////////////////////////////
//
// (c) 2007 BinaryComponents Ltd.  All Rights Reserved.
//
// http://www.binarycomponents.com/
//
/////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections;
using System.Collections.Generic;
using SuperListEx.Sections;

namespace SuperListEx
{
	public class SelectedItemsCollection : ICollection<RowIdentifier>
    {
        #region vars
        private Dictionary<RowIdentifier, int> _selectedRows = new Dictionary<RowIdentifier, int>();
        private readonly ListControl _listControl;
        private WeakReference _selectedItemsArrayRef = null;        
        #endregion

        #region Events
        /// <summary>
        /// This event is fired when the selected items are changed in some way
        /// </summary>
        public event DataChangedHandler DataChanged;
        #endregion

        #region Properties
        public RowIdentifier this[int i]
        {
            get
            {
                return new List<RowIdentifier>(GetSortedSelectionList())[i];
            }
        }

        /// <summary>
        /// The number of selected items
        /// </summary>
        public int Count
        {
            get
            {
                return _selectedRows.Count;
            }
        }

        /// <summary>
        /// THIS IS ALWAYS FALSE
        /// </summary>
        public bool IsReadOnly
        {
            get
            {
                return false;
            }
        }
        #endregion

        #region Public Functions
        /// <summary>
        /// Selects one item
        /// </summary>
        /// <param name="o"></param>
        public void Add(object o)
        {
            Add(new NonGroupRow(o));
        }

        /// <summary>
        /// Selects many items
        /// </summary>
        public void AddRange(IEnumerable items)
        {
            foreach (object o in items)
            {
                Add(o);
            }
        }

        /// <summary>
        /// Selects one item
        /// </summary>
        public void Add(RowIdentifier ri)
        {
            int i = GetPositionFromItem(ri);
            if (i == -1)
            {
                //
                // Not found so force any deferred updates to be processed.
                i = GetPositionFromItem(ri);
                if (i == -1)
                {
                    throw new ArgumentException("ri cannot be added to the selected lists as it doens't exist in the main list");
                }
            }

            _selectedRows.Add(ri, i);
            _listControl.ListSection.LazyLayout();
            _selectedItemsArrayRef = null;
            OnDataChanged(ChangeType.Inserted, ri);
        }

        /// <summary>
        /// Removes and item from the list
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool Remove(RowIdentifier item)
        {
            return RemoveInternal(item);
        }

        /// <summary>
        /// Selects all items in the list
        /// </summary>
        public void SelectAll()
        {
            _listControl.ListSection.SelectAll();
        }

        public List<RowIdentifier> ToList()
        {
            return new List<RowIdentifier>(GetSortedSelectionList());
        }

        public void Clear()
        {
            if (DataChanged != null)
            {
                List<RowIdentifier> items = new List<RowIdentifier>(GetSortedSelectionList());
                _selectedRows.Clear();
                if (items.Count > 0)
                {
                    OnDataChanged(ChangeType.Deleted, items.ToArray());
                }
            }
            else
            {
                _selectedRows.Clear();
            }
            ClearCache();
        }

        public bool Contains(RowIdentifier item)
        {
            return _selectedRows.ContainsKey(item);
        }
        #endregion

        #region Private Functions
        private int GetPositionFromItem(RowIdentifier ri)
        {
            return _listControl.Items.IndexOf(ri.Items[0]);
        }

        private int GetPositionFromItem(object item)
        {
            return _listControl.Items.IndexOf(item);
        }

        private int RowIdentifierComparison(RowIdentifier x, RowIdentifier y)
        {
            if (x.Equals(y))
            {
                return 0;
            }

            int posX = _selectedRows[x];
            int posY = _selectedRows[y];
            if (posX != posY)
            {
                return posX - posY;
            }

            //
            //	Positions are the same so use the group positions if they have one. 
            //	Groups are always first compared with non grouped items.
            int groupPosX = x.GroupColumns.Count - 1;
            int groupPosY = y.GroupColumns.Count - 1;
            if (groupPosX > 0 && groupPosY == -1)
            {
                return -1;
            }
            if (groupPosY > 0 && groupPosX == -1)
            {
                return 1;
            }
            return groupPosX - groupPosY;
        }

        private IEnumerable<RowIdentifier> GetSortedSelectionList()
        {
            RowIdentifier[] selectedItemsArray = _selectedItemsArrayRef == null ? null : (RowIdentifier[])_selectedItemsArrayRef.Target;
            if (selectedItemsArray == null)
            {
                List<RowIdentifier> items = new List<RowIdentifier>(_selectedRows.Count);
                List<object> keys = new List<object>(_selectedRows.Count);
                foreach (KeyValuePair<RowIdentifier, int> kv in _selectedRows)
                {
                    items.Add(kv.Key);
                    keys.Add(kv.Value);
                }
                RowIdentifier[] itemsArray = items.ToArray();
                Array.Sort(keys.ToArray(), itemsArray);
                selectedItemsArray = items.ToArray();
                _selectedItemsArrayRef = new WeakReference(selectedItemsArray);
            }
            return selectedItemsArray;
        }

        private void OnDataChanged(ChangeType changeType, params RowIdentifier[] items)
        {
            if (DataChanged != null)
            {
                SelectedItemsChangedEventArgs eventArgs = new SelectedItemsChangedEventArgs(items, changeType);
                DataChanged(_listControl, eventArgs);
            }
        }

        private void ClearCache()
        {
            _selectedItemsArrayRef = null;
        }
        #endregion

        #region Internal Functions
        internal bool RemoveInternal(RowIdentifier rowIdentifier)
        {
            ClearCache();
            if (_selectedRows.ContainsKey(rowIdentifier))
            {
                _selectedRows.Remove(rowIdentifier);
                OnDataChanged(ChangeType.Deleted, rowIdentifier);
                return true;
            }
            return false;
        }

        internal void RemoveRangeInternal(IEnumerable<RowIdentifier> rowIdentifiers)
        {
            ClearCache();
            if (DataChanged == null)
            {
                foreach (RowIdentifier ri in rowIdentifiers)
                {
                    _selectedRows.Remove(ri);
                }
            }
            else
            {
                List<RowIdentifier> toProcess = new List<RowIdentifier>(rowIdentifiers);
                List<RowIdentifier> items = new List<RowIdentifier>(toProcess.Count);
                List<object> keys = new List<object>(items.Count);
                foreach (RowIdentifier ri in toProcess)
                {
                    int position;
                    if (_selectedRows.TryGetValue(ri, out position))
                    {
                        items.Add(ri);
                        keys.Add(position);
                        _selectedRows.Remove(ri);
                    }
                }
                RowIdentifier[] itemsArray = items.ToArray();
                Array.Sort(keys.ToArray(), itemsArray);
                OnDataChanged(ChangeType.Deleted, itemsArray);
            }
        }

        internal SelectedItemsCollection(ListControl listControl)
        {
            _listControl = listControl;
        }

        internal bool IsSelected(RowIdentifier rowIdentifier)
        {
            return _selectedRows.ContainsKey(rowIdentifier);
        }

        internal int IndexFromRowIdentifier(RowIdentifier ri)
        {
            return _selectedRows[ri];
        }

        /// <summary>
        /// Symantically clears the list and adds the items given, but will
        /// only call data changed on the differences in terms of the final 
        /// outcome of deleted / inserts items.
        /// </summary>
        /// <param name="items"></param>
        /// <returns>The number of rows changed</returns>
        internal int ClearAndAdd(params RowIdentifier[] items)
        {
            List<RowIdentifier> deletedItems = new List<RowIdentifier>();
            List<RowIdentifier> insertedItems = new List<RowIdentifier>();

            Dictionary<RowIdentifier, int> selectedRows = new Dictionary<RowIdentifier, int>();
            int itemsAlreadyInListCount = 0;
            foreach (RowIdentifier item in items)
            {
                bool inList = selectedRows.ContainsKey(item);
                if (!inList)
                {
                    int pos = GetPositionFromItem(item);
                    if (pos != -1)
                    {
                        selectedRows.Add(item, pos);
                    }
                }
                if (_selectedRows.ContainsKey(item))
                {
                    itemsAlreadyInListCount++;
                }
                else
                {
                    if (!inList)
                    {
                        insertedItems.Add(item);
                    }
                }
            }
            if (itemsAlreadyInListCount < _selectedRows.Count)
            {
                foreach (RowIdentifier item in _selectedRows.Keys)
                {
                    if (!selectedRows.ContainsKey(item))
                    {
                        deletedItems.Add(item);
                    }
                }
            }
            _selectedRows = selectedRows;
            ClearCache();
            if (deletedItems.Count > 0)
            {
                OnDataChanged(ChangeType.Deleted, deletedItems.ToArray());
            }
            if (insertedItems.Count > 0)
            {
                OnDataChanged(ChangeType.Inserted, insertedItems.ToArray());
            }
            return deletedItems.Count + insertedItems.Count;
        }

        internal void AddInternal(RowIdentifier item)
        {
            ClearCache();

            if (!_selectedRows.ContainsKey(item))
            {
                int pos = GetPositionFromItem(item);
                if (pos != -1)
                {
                    _selectedRows.Add(item, pos);
                    OnDataChanged(ChangeType.Inserted, item);
                }
            }
        }

        internal void AddRangeInternal(IEnumerable<RowIdentifier> selectedItems)
        {
            ClearCache();
            if (DataChanged == null)
            {
                foreach (RowIdentifier ri in selectedItems)
                {
                    if (!_selectedRows.ContainsKey(ri))
                    {
                        int pos = GetPositionFromItem(ri);
                        if (pos != -1)
                        {
                            _selectedRows.Add(ri, pos);
                        }
                    }
                }
            }
            else
            {
                List<RowIdentifier> items = new List<RowIdentifier>(selectedItems);
                foreach (RowIdentifier ri in selectedItems)
                {
                    if (!_selectedRows.ContainsKey(ri))
                    {
                        int pos = GetPositionFromItem(ri);
                        if (pos != -1)
                        {
                            _selectedRows.Add(ri, pos);
                            items.Add(ri);
                        }
                    }
                }
                if (items.Count > 0)
                {
                    items.Sort(RowIdentifierComparison);
                    OnDataChanged(ChangeType.Inserted, items.ToArray());
                }
            }
        }
        #endregion

		void ICollection<RowIdentifier>.CopyTo( RowIdentifier[] array, int arrayIndex )
		{
			throw new Exception( "The method or operation is not implemented." );
		}

		IEnumerator<RowIdentifier> IEnumerable<RowIdentifier>.GetEnumerator()
		{
			foreach( RowIdentifier ri in GetSortedSelectionList() )
			{
				yield return ri;
			}
		}

		IEnumerator IEnumerable.GetEnumerator()
		{
			return GetSortedSelectionList().GetEnumerator();
		}
	}
}